《Linux – Linux高级编程 – 第一部分 标准IO及文件IO》第4章 静态库与动态库的使用

库是一种软件组件技术,库里面封装了数据和函数。库的使用可以使程序模块化。Windows系统包括静态链接库(.lib文件)和动态链接库(.dll文件)。Linux通常把库文件存放在/usr/lib或/lib目录下。
Linux库文件名由:前缀lib、库名和后缀3部分组成,其中动态链接库以.so最为后缀,静态链接库通常以.a作为后缀。

Linux 应用开发通常要考虑三个问题,即:

1)在 Linux 应用程序开发过程中遇到过标准库链接在不同 Linux 版本下不兼容的问题;
2)在 Linux 静态库的制作过程中发现有别于 Windows 下静态库的制作方法;
3)在 Linux 应用程序链接第三方库或者其他静态库的时候发现链接顺序的烦人问题。本文就这三个问题针对 Linux 下标准库链接和如何巧妙构建 achrive(*.a) 展开相关介绍。

在程序中使用使用静态库和动态库时,他们载入的顺序是不同的。静态库的代码在编译时就拷贝的应用程序中,这样的优点是节省编译时间。

动态链接库时程序在开始运行后调用库函数时才被载入。

Linux 应用程序因为 Linux 版本的众多与各自独立性,在工程制作与使用中必须熟练掌握如下两点才能有效地工作和理想地运行。

1)Linux 下标准库链接的三种方式(全静态 , 半静态 (libgcc,libstdc++), 全动态)及其各自利弊。
2)Linux 下如何巧妙构建 achrive(*.a),并且如何设置链接选项来解决 gcc 比较特别的链接库的顺序问题。

在Linux操作系统中,普遍使用ELF格式作为可执行程序或者程序生成过程中的中间格式。ELF(Executable and Linking Format,可执行连接格式)。

ELF文件格式包括三种主要的类型:可执行文件、可重定向文件、共享库
1.可执行文件(应用程序)
可执行文件包含了代码和数据,是可以直接运行的程序。

2.可重定向文件(*.o)
可重定向文件又称为目标文件,它包含了代码和数据(这些数据是和其他重定位文件和共享的object文件一起连接时使用的)。

*.o文件参与程序的连接(创建一个程序)和程序的执行(运行一个程序),它提供了一个方便有效的方法来用并行的视角看待文件的内容,这些.o文件的活动可以反映出不同的需要。
Linux下,我们可以用gcc -c编译源文件时可将其编译成
.o格式。

3.共享文件(*.so)
也称为动态库文件,它包含了代码和数据(这些数据是在连接时候被连接器ld和运行时动态连接器使用的)。动态连接器可能称为ld.so.1,libc.so.1或者 ld-linux.so.1。

库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行。库分静态库和动态库两种。二者的不同点在于代码被载入的时刻不同。

静态库:这类库的名字一般是libxxx.a,xxx为库的名字。静态库在程序编译时会被连接到目标代码中,利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,它的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为它的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。

动态库:这类库的名字一般是libxxx.M.N.so,同样的xxx为库的名字,M是库的主版本号,N是库的副版本号。当然也可以不要版本号,但名字必须有。相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。linux系统有几个重要的目录存放相应的函数库,如/lib /usr/lib。

当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。然而,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记指明当程序执行时,首先必须载入这个库。由于动态库节省空间,linux下进行连接的缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。

4.1静态库

前面已经介绍了静态库的概念,下面我们通过一个实例来看静态库的使用

4.1.1生成静态库

1)这里准备了两个库的源码st1 、st2用它们来制作库libmytest.a,如下

$ ls -l  
total 12
-rw-rw-r-- 1 ouxiaolong ouxiaolong 390 Mar 19 18:04 main.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong 362 Mar 19 18:03 st1.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong 361 Mar 19 18:04 st2.c
$ cat st1.c  
#include <stdio.h>  
void print1()  
{  
    printf("Hello!I am st1!\n ");  
}  
$ cat st2.c  
#include <stdio.h>  
void print2()  
{  
    printf("Hello!I am st2!\n");  
}  
$ cat main.c  
#include <stdio.h>  
int main(int argc, const char *argv[])  
{  
    print1();  
    print2();  

    return 0;  
}  

2) 生成libmytest.a文件
创建静态库用ar命令,它将很多.o转换成.a

$ gcc -c st1.c st2.c  
$ ar crs libmytest.a st1.o st2.o  
$ ls -l  
total 24
-rw-rw-r-- 1 ouxiaolong ouxiaolong 3214 Mar 19 18:06 libmytest.a
-rw-rw-r-- 1 ouxiaolong ouxiaolong  390 Mar 19 18:04 main.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong  362 Mar 19 18:03 st1.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong 1504 Mar 19 18:06 st1.o
-rw-rw-r-- 1 ouxiaolong ouxiaolong  361 Mar 19 18:04 st2.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong 1496 Mar 19 18:06 st2.o
$ file libmytest.a   
libmytest.a: current ar archive  

静态库文件libmytest.a已经生成,用file命令查看其属性,发现它确实是归档压缩文件。用ar -t libmytest.a可以查看一个静态库包含了那些obj文件:

$ ar -t libmytest.a   
st1.o  
st2.o 

4.1.2使用静态库

1.直接使用

$gcc -o test main.c libmytest.a

2.双L链接法

$gcc -o test main.c  -lmytest  -L.(库所在目录,.表示当前目录)

3.单L链接法(需要配置环境变量LIBRARY_PATH,把库放在所在目录配置)

$gcc -o test main.c  -lmytest 

前面我们已经写好了main.c,现在测试一下

$ gcc -o test main.c -L. -lmytest  
$ ls -l  
total 36
-rw-rw-r-- 1 ouxiaolong ouxiaolong 3214 Mar 19 18:06 libmytest.a
-rw-rw-r-- 1 ouxiaolong ouxiaolong  390 Mar 19 18:04 main.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong  362 Mar 19 18:03 st1.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong 1504 Mar 19 18:06 st1.o
-rw-rw-r-- 1 ouxiaolong ouxiaolong  361 Mar 19 18:04 st2.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong 1496 Mar 19 18:06 st2.o
-rwxrwxr-x 1 ouxiaolong ouxiaolong 8732 Mar 19 18:08 test
$ ./test   
Hello! I am st1!  
Hello! I am st2!  

这里gcc的参数-L是告诉编译器库文件的路径是当前目录,-l是告诉编译器要使用的库的名字叫mytest。

4.1.3静态库总结

1.在一个头文件中声明静态库所导出的函数。
2.在一个源文件中实现静态库所导出的函数。
3.编译源文件,生成可执行代码。
4.将可执行代码所在的目标文件加入到某个静态库中,并将静态库拷贝到系统默认的存放库文件的目录下。

下面通过一个例子来说明:

源文件:mylib.c

#include <stdio.h>
void welcome(void)
{
    printf("welcome to libmylib\n");
}

1>编译mylib.c生成目标文件:gcc -o mylib.o -c mylib.c
2>将目标文件加入到静态库中:ar -rcs libmylib.a mylib.o

【注】库文件名以lib开头,以.a结尾。
3>将静态库copy到Linux的库目录(/usr/lib或者/lib)下:(也可以不拷贝,注意配置环境变量)

cp  libmylib.a  /usr/lib/libmylib.a //管理员身份

编写调用库函数的测试程序test.c:

#include <stdio.h>
int main(void)
{
    printf("create and use library:\n");
    welcome(); 
    return 0;
}

4>使用静态库编译:gcc -o test test.c -lmylib
这里注意,编译时无需带上前缀和后缀。

5>运行可执行程序test: ./test

create and use library:
welcome to libmylib

在Linux下,可以使用ar命令来创建和修改静态库。

这些在linux下man ar一下就可以得到参数,这里说明几个常用的

d:从库中删除成员文件。
r:在库中加入成员文件,若存在,则替换。
c:创建一个库。
s:无论ar命令是否修改了库内容,都强制重新生成库符号表。

其他的命令用时再man。
【注】gcc -static 文件.c [ -o 文件]//全部使用静态库的方法

4.2动态库

动态库的基本概念
1.动态链接库是程序运行时加载的库,当动态链接库正确安装后,所有的程序都可以使用动态库来运行程序。动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址是相对地址,不是绝对地址,其真实地址在调用动态库的程序加载时形成。

2.动态链接库的名称有别名(soname), 真名(realname)和链接名(linker name)。别名由一个前缀lib,然后是库的名字,再加上一个后缀“.so”构成("libxxx.so")。真名是动态链接库真实名称,一般总是在别名的基础加上一个小版本号,发布版本等构成。除此之外,还有一个链接名,即程序链接时使用的库的名字。

3.在动态链接库安装的时候,总是复制文件到某个目录下,然后用一个软连接生成别名,在库文件进行更新的时候,仅仅更新软链接即可。

下面我们通过一个实例来学习如何生成动态库和使用动态库

4.2.1生成动态库

1)当前文件夹下有下面四个文件

$ ls -l  
total 16
-rw-rw-r-- 1 ouxiaolong ouxiaolong 361 Mar 19 18:12 dy1.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong 366 Mar 19 18:12 dy2.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong 410 Mar 19 18:13 main.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong 390 Mar 19 18:11 mylib.h

文件内容分别为:

$ cat mylib.h   
#ifndef _MYLIB_H_  
#define _MYLIB_H_  

#include <stdio.h>  

void print1();  
void print2();  

#endif  
$ cat dy1.c  
#include "mylib.h"  

void print1()  
{  
    printf("My first shared lib!\n");  
}  
$ cat dy2.c  
#include "mylib.h"  
void print2()  
{  
    printf("My second shared lib!\n");  
}  
$ cat main.c  
#include "mylib.h"  

int main(int argc, char *argv[])  
{  
    print1();  
    print2();  
    return 0;  
}  

2)这里我们将dy1.c与dy2.c用来创建动态库

$ gcc -fPIC -Wall -c dy1.c dy2.c  
$ gcc -shared -o libtest.so dy1.o dy2.o  

这里 -fPIC 创建与地址无关的编译程序,-shared指定生成动态链接库。
我们也可以一步到位

$ gcc -o libtest.so -fPIC -shared dy1.c dy2.c  

我们可以看到下面已经生成了一个libtest.so

$ ls -l  
total 32
-rw-rw-r-- 1 ouxiaolong ouxiaolong  361 Mar 19 18:12 dy1.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong 1552 Mar 19 18:14 dy1.o
-rw-rw-r-- 1 ouxiaolong ouxiaolong  366 Mar 19 18:12 dy2.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong 1552 Mar 19 18:14 dy2.o
-rwxrwxr-x 1 ouxiaolong ouxiaolong 8063 Mar 19 18:14 libtest.so
-rw-rw-r-- 1 ouxiaolong ouxiaolong  410 Mar 19 18:13 main.c
-rw-rw-r-- 1 ouxiaolong ouxiaolong  390 Mar 19 18:11 mylib.h

4.2.2使用动态链接库

在编译程序时,使用动态链接库和静态库是一致的,使用”-l库名”的方式,在生成可执行文件的时候会链接库文件。使用如下命令:

$ gcc -o test main.c -L. -ltest  
$ ls  
dy1.c  dy2.c  libtest.so  main.c  mylib.h  test  

这里 -L 指定动态链接库的路劲,-ldtest链接库函数test 。-ltest是动态库的调用规则。Linux系统下的动态库命名方式是lib.so,而在链接时表示位-l,*是自己命名的库名。
我们可以看到这里已经生成了test可执行文件,我们可以执行一下:

$ ./test  
./test: error while loading shared libraries: libtest.so: cannot open   
shared object file: No such file or directory  

可以发现发生了错误,这是因为程序运行时没有找到动态链接库造成的。程序编译时链接动态库和运行时使用动态链接库的概念是不同的,在运行时,程序链接的动态链接库需要在系统目录下才行。

这就到了动态库的路径问题,有三种方法:
1)把库拷贝到/usr/lib和/lib目录下:

$ sudo cp libtest.so /lib  

【注】这里要超级用户权限sudo
我们看一下执行结果:

$ ./test   
My first shared lib!  
My second shared lib!  

这里执行结果正确。

2)在 LD_LIBRARY_PATH 环境变量中加上库所在路径

$ export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH  

我们可以看到:libtest.so 的路径已经存在

$ ldd test  
    linux-gate.so.1 =>  (0xb774b000)  
    libtest.so => /home/fs/qiang/lib/dylib/libtest.so (0xb7746000)  
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb758d000)  
    /lib/ld-linux.so.2 (0xb774c000)  

我们可以看一下结果:

$ ./test   
My first shared lib!  
My second shared lib!  

也能得到正确结果。

3)添加/etc/ld.so.conf.d/*.conf文件。把库所在的路径加到文件末尾,并执行ldconfig刷新。

$ cd /etc/ld.so.conf.d/  
$ ls  

6pa6Lq.png

$ sudo vi my.conf  
$ cat my.conf   
/home/ouxiaolong/lib/libtest.so  

在/etc/ld.so.conf.d/下建立 my.conf 里面只有一句/home/ouxiaolong/lib/libtest.so即libtest.so的路径,然后执行ldconfig刷新即可。

4.2.3动态库总结

1.编译动态库
方法一:
命令:gcc -fPIC -Wall -c .c
命令:gcc -shared -o lib.so .o

方法二:
命令:gcc .c .c .c -fPIC -shared -o lib.so

2.安装路径
命令:cp lib.so /usr/lib/lib.so//管理员身份

3.动态库的链接
命令:gcc -o < preject_name> < tag_name>.c -L. -l< name>
或者:命令:gcc < tag_name>.c -L. -l< name> -o < preject_name>

4.运行可执行程序
【注意】动态库的路径问题
把库拷贝到/usr/lib目录下

ldd a.out 可以查看相关的共享库函数

Related posts

Leave a Comment